package com.zliuyno.io.testreactor;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;


/**
 * @author liuyno
 * @date 2020/11/25 - 19:28
 */
public class SelectorThread implements Runnable {
    //每线程对应一个selector
    //多线程情况下，该主机，该程序的并发客户端被分配到多个selector上
    //注意每个客户端，只绑定到一个selector

    Selector selector = null;
    LinkedBlockingQueue<Channel> lbq = new LinkedBlockingQueue<>();
    SelectorThreadGroup stg;


    SelectorThread(SelectorThreadGroup stg) {
        try {
            this.stg = stg;
            selector = Selector.open();


        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    @Override
    public void run() {
        //Loop
        while (true) {
            try {
                //1.select()
//                System.out.println(Thread.currentThread().getName() + ":before select。。。。" + selector.keys().size());
                int nums = selector.select();   //阻塞    wakeup()
//                Thread.sleep(1000);   //这绝对不是解决方案  演示使用
//                System.out.println(Thread.currentThread().getName() + ":arfter select。。。。" + selector.keys().size());
                if (nums > 0) {
                    //2.处理selectkeys
                    Set<SelectionKey> keys = selector.selectedKeys();
                    Iterator<SelectionKey> iterator = keys.iterator();
                    while (iterator.hasNext()) { //线性处理过程
                        SelectionKey key = iterator.next();
                        iterator.remove();
                        if (key.isAcceptable()) {    //复杂，接收客户端的过程（接收之后要注册，多线程下，新的客户端注册到哪里呢？）
                            acceptHandler(key);
                        } else if (key.isReadable()) {
                            readHandler(key);
                        } else if (key.isWritable()) {

                        }
                    }
                }

                //3.处理一些task
                if (!lbq.isEmpty()) {   //队列是个什么？  堆里的对象，线程的栈是独立的，堆是共享的
                    //只有方法的逻辑、方法的本地变量是线程隔离的
                    Channel c = lbq.take();
                    if (c instanceof ServerSocketChannel) {
                        ServerSocketChannel server = (ServerSocketChannel) c;
                        server.register(selector, SelectionKey.OP_ACCEPT);
                    } else if (c instanceof SocketChannel) {
                        SocketChannel client = (SocketChannel) c;
                        ByteBuffer buffer = ByteBuffer.allocateDirect(4096);
                        client.register(selector, SelectionKey.OP_READ, buffer);
                    }
                }
            } catch (IOException | InterruptedException e) {
                e.printStackTrace();
            }


        }
    }

    private void readHandler(SelectionKey key) {
        System.out.println(Thread.currentThread().getName()+"...readHandler.........");
        ByteBuffer buffer = (ByteBuffer) key.attachment();
        SocketChannel client = (SocketChannel) key.channel();
        buffer.clear();
        while (true) {
            try {
                int num = client.read(buffer);
                if (num > 0) {
                    buffer.flip();
                    while (buffer.hasRemaining()) {
                        client.write(buffer);
                    }
                    buffer.clear();
                } else if (num == 0) {
                    break;
                } else if (num < 0) {
                    //客户端断开了
                    System.out.println("client: " + client.getRemoteAddress() + "........closed.......");
                    key.cancel();
                    break;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }


    }

    private void acceptHandler(SelectionKey key) {
        System.out.println(Thread.currentThread().getName()+"...acceptHandler.........");

        ServerSocketChannel server = (ServerSocketChannel) key.channel();
        try {
            SocketChannel client = server.accept();
            client.configureBlocking(false);
            //需要 choose a selector and register!!
            stg.nextSelectorV3(client);


        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public void setWorker(SelectorThreadGroup stgWorker) {
        this.stg = stgWorker;
    }
}
